package com.webchat.user.service.relation;


import com.webchat.common.bean.APIPageResponseBean;
import com.webchat.common.constants.WebConstant;
import com.webchat.common.enums.AccountRelationStatusEnum;
import com.webchat.common.enums.AccountRelationTypeEnum;
import com.webchat.common.enums.RedisKeyEnum;
import com.webchat.common.enums.RoleCodeEnum;
import com.webchat.common.enums.messagequeue.MessageQueueEnum;
import com.webchat.common.service.RedisService;
import com.webchat.common.service.messagequeue.producer.MessageQueueProducer;
import com.webchat.common.util.DateUtils;
import com.webchat.common.util.ThreadPoolExecutorUtil;
import com.webchat.domain.vo.request.mess.ChatMessageRequestVO;
import com.webchat.domain.vo.response.UserBaseResponseInfoVO;
import com.webchat.user.repository.dao.IAccountRelationDAO;
import com.webchat.user.repository.entity.AccountRelationEntity;
import com.webchat.user.service.UserService;
import jakarta.transaction.Transactional;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.DefaultTypedTuple;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.util.Assert;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

public abstract class AbstractAccountRelationService implements AccountRelationValidator, AccountRelationWrapper {

    @Autowired
    private IAccountRelationDAO accountRelationDAO;

    @Autowired
    UserService userService;

    @Autowired
    private RedisService redisService;

    @Autowired
    private MessageQueueProducer<ChatMessageRequestVO, Long> messageQueueProducer;

    private UserBaseResponseInfoVO sourceAccountInfo;

    private UserBaseResponseInfoVO targetAccountInfo;

    protected void init(String sourceAccount, String targetAccount) {
        this.sourceAccountInfo = userService.getUserBaseInfoByUserId(sourceAccount);
        this.targetAccountInfo = userService.getUserBaseInfoByUserId(targetAccount);
    }

    /**
     * 订阅关系类型，需由具体实现类返回
     *
     *
     * @return
     */
    protected abstract AccountRelationTypeEnum getRelationType();

    /**
     * 获取订阅状态
     *
     * @return
     */
    protected AccountRelationStatusEnum getSubscribeStatus() {
        return AccountRelationStatusEnum.CONFIRMED;
    }

    /**
     * 是否需要异步执行后置处理流程
     *
     * @return
     */
    protected abstract boolean isAsyncDoAfterComplete();

    /**
     * 完成核心流程后的业务处理，由各实现类自己实现
     * 比如：
     * 1、基于不同关系的缓存构建、刷新
     */
    protected abstract void doAfterComplete(Long id, String sourceAccount, String targetAccount, boolean subscribe);

    /**
     * 完成核心流程后的业务处理，由各实现类自己实现
     * 比如：
     * 1、基于不同关系的缓存构建、刷新
     */
    protected void execAfterComplete(Long id, String sourceAccount, String targetAccount, boolean subscribe) {
        /**
         * 后置流程公共逻辑实现
         */
        if (!subscribe) {
            this.removeTargetAccountRelationListCache(sourceAccount, targetAccount);
        }
        /**
         * 差异化逻辑各个实现类自己支持
         */
        this.doAfterComplete(id, sourceAccount, targetAccount, subscribe);
    }

    /**
     * 异步执行后置业务处理流程
     *
     * @param id
     * @param sourceAccount
     * @param targetAccount
     */
    protected void asyncDoAfterComplete(Long id, String sourceAccount, String targetAccount, boolean subscribe) {

        ThreadPoolExecutorUtil.execute(() -> execAfterComplete(id, sourceAccount, targetAccount, subscribe));
    }

    private void doExecAfterComplete(Long id, String sourceAccount, String targetAccount, boolean subscribe) {
        if (isAsyncDoAfterComplete()) {
            this.asyncDoAfterComplete(id, sourceAccount, targetAccount, subscribe);
        } else {
            this.execAfterComplete(id, sourceAccount, targetAccount, subscribe);
        }
    }

    /**
     * 前置基础校验
     *
     * @param sourceAccount
     * @param targetAccount
     */
    @Override
    public void validateAccountRequest(String sourceAccount, String targetAccount) {
        Assert.notNull(sourceAccountInfo, "source account is null!");
        Assert.isTrue(RoleCodeEnum.isUserRole(sourceAccountInfo.getRoleCode()), "source account role is not support!");
        Assert.notNull(targetAccountInfo, "target account is null!");
        Assert.isTrue(!RoleCodeEnum.BLACK.getCode().equals(targetAccountInfo.getRoleCode()), "target account is black list user!");
    }

    /**
     * 关系订阅接口
     *
     * @param sourceAccount
     * @param targetAccount
     * @return
     */
    @Transactional
    @Override
    public Boolean subscribe(String sourceAccount, String targetAccount) {
        this.init(sourceAccount, targetAccount);
        /**
         * 前置参数校验
         */
        this.validateAccountRequest(sourceAccount, targetAccount);
        /**
         * 核心订阅流程处理
         */
        Long id = this.doSubscribe(sourceAccount, targetAccount);
        /**
         * 执行后置流程
         */
        this.doExecAfterComplete(id, sourceAccount, targetAccount, Boolean.TRUE);
        return true;
    }

    /**
     * 执行取消订阅的持久化更新逻辑
     *
     * @param sourceAccount
     * @param targetAccount
     * @return
     */
    @Transactional
    @Override
    public Boolean unsubscribe(String sourceAccount, String targetAccount) {
        /**
         * 前置参数校验
         */
        this.validateAccountRequest(sourceAccount, targetAccount);
        /**
         * 核心订阅流程处理
         */
        Long id = this.doUnSubscribe(sourceAccount, targetAccount);
        /**
         * 执行后置流程
         */
        this.doExecAfterComplete(id, sourceAccount, targetAccount, Boolean.FALSE);
        return true;
    }

    /**
     * 查询账号关系列表，走redis
     *
     * @param account
     * @param pageNo
     * @param pageSize
     * @return
     */
    @Override
    public APIPageResponseBean<UserBaseResponseInfoVO> listRelations(String account, Integer pageNo, Integer pageSize) {

        Integer type = this.getRelationType().getType();
        String prefix = "SAFE";
        String cacheKey = this.getRelationListRedisKey(account, type);
        String safeCacheKey = this.getRelationListRedisKey(prefix, account, type);
        if (!redisService.exists(cacheKey) && !redisService.exists(safeCacheKey)) {
            // 失效/没有好友关系, 刷新账号管理缓存列表
            this.initAccountRelationListCache(account, type);
            redisService.set(safeCacheKey, WebConstant.CACHE_NONE, 5 * 60);
        }
        Long total = redisService.zsize(cacheKey);
        List<UserBaseResponseInfoVO> relactionAccountList = new ArrayList<>();
        if (total > 0) {
            Set<String> relationCaches = redisService.zreverseRange(cacheKey, (pageNo - 1) * pageSize, pageNo * pageSize);
            relactionAccountList = userService.batchGetUserListInfoFromCache(relationCaches);
        }
        return APIPageResponseBean.success(pageNo, pageSize, total, relactionAccountList);
    }

    /**
     * 查询账号下的所有订阅用户列表
     *
     * @param account
     * @return
     */
    @Override
    public Set<String> getAllSubscriber(String account) {
        Integer type = this.getRelationType().getType();
        String prefix = "SAFE";
        String cacheKey = this.getRelationListRedisKey(account, type);
        String safeCacheKey = this.getRelationListRedisKey(prefix, account, type);
        if (!redisService.exists(cacheKey) && !redisService.exists(safeCacheKey)) {
            // 失效/没有好友关系, 刷新账号管理缓存列表
            this.initAccountRelationListCache(account, type);
            redisService.set(safeCacheKey, WebConstant.CACHE_NONE, 5 * 60);
        }
        return redisService.zreverseRange(cacheKey, 0, Integer.MAX_VALUE);
    }

    @Override
    public boolean isSubscribe(String userAccount, String account) {
        Integer type = this.getRelationType().getType();
        String prefix = "SAFE";
        String cacheKey = this.getRelationListRedisKey(account, type);
        String safeCacheKey = this.getRelationListRedisKey(prefix, account, type);
        if (!redisService.exists(cacheKey) && !redisService.exists(safeCacheKey)) {
            // 失效/没有好友关系, 刷新账号管理缓存列表
            this.initAccountRelationListCache(account, type);
            redisService.set(safeCacheKey, WebConstant.CACHE_NONE, 5 * 60);
        }
        return redisService.zIsExist(cacheKey, userAccount);
    }

    @Override
    public Map<String, Boolean> batchIsSubscribe(List<String> userAccounts, String account) {

        Integer type = this.getRelationType().getType();
        String prefix = "SAFE";
        String cacheKey = this.getRelationListRedisKey(account, type);
        String safeCacheKey = this.getRelationListRedisKey(prefix, account, type);
        if (!redisService.exists(cacheKey) && !redisService.exists(safeCacheKey)) {
            // 失效/没有好友关系, 刷新账号管理缓存列表
            this.initAccountRelationListCache(account, type);
            redisService.set(safeCacheKey, WebConstant.CACHE_NONE, 5 * 60);
        }

        Map<String, Boolean> subMap = new HashMap<>();
        String[] userAccountArray = userAccounts.toArray(new String[userAccounts.size()]);
        List<Double> scores = redisService.batchZIsExist(cacheKey, userAccountArray);
        for (int i = 0; i < userAccounts.size(); i++) {
            String sourceAccount = userAccounts.get(i);
            boolean isSub = scores.get(i) != null;
            subMap.put(sourceAccount, isSub);
        }
        return subMap;
    }

    /**
     * 添加targetAccount到sourceAccount的好友列表缓存
     *
     * @param sourceAccount
     * @param targetAccount
     */
    protected void addTargetAccountRelationListCache(String sourceAccount, String targetAccount) {
        // 获取好友列表redis key
        AccountRelationTypeEnum relationType = getRelationType();
        String cacheKey = this.getRelationListRedisKey(sourceAccount, relationType.getType());
        if (!redisService.exists(cacheKey)) {
            // 第一次订阅或者缓存失效
            // 查询数据库，主动刷新redis
            this.initAccountRelationListCache(sourceAccount, relationType.getType());
            redisService.zadd(cacheKey, targetAccount, DateUtils.getCurrentTimeMillis(),
                    RedisKeyEnum.ACCOUNT_RELATION_LIST_CACHE.getExpireTime());
        }
        /**
         * 添加targetAccount到sourceAccount的好友列表缓存，当前时间作为sorted set 的score，后续可用于倒序查询
         */
        redisService.zadd(cacheKey, targetAccount, DateUtils.getCurrentTimeMillis(),
                RedisKeyEnum.ACCOUNT_RELATION_LIST_CACHE.getExpireTime());
    }

    /**
     * 删除用户关系账号
     *
     * @param account
     * @param removeAccount
     */
    protected void removeTargetAccountRelationListCache(String account, String removeAccount) {
        // 获取好友列表redis key
        AccountRelationTypeEnum relationType = AccountRelationTypeEnum.getByTargetAccountRoleCode(
                targetAccountInfo.getRoleCode());
        String cacheKey = this.getRelationListRedisKey(account, relationType.getType());
        redisService.zremove(cacheKey, removeAccount);
    }

    /**
     * 初始化主动刷新账号关系列表缓存
     *
     * @param account
     * @param type
     */
    public void initAccountRelationListCache(String account, Integer type) {
        List<AccountRelationEntity> accountRelationEntities =
                this.getAccountRelationList(account, type);
        if (CollectionUtils.isEmpty(accountRelationEntities)) {
           return;
        }
        String cacheKey = this.getRelationListRedisKey(account, type);
        Set<ZSetOperations.TypedTuple<String>> tuples = accountRelationEntities.stream().
                map(ar -> {
                    String value = ar.getSourceAccount().equals(account) ? ar.getTargetAccount() : ar.getSourceAccount();
                    Double score = Double.valueOf(ar.getUpdateDate().getTime());
                    return new DefaultTypedTuple<>(value, score);
                }).collect(Collectors.toSet());
        redisService.zadd(cacheKey, tuples, RedisKeyEnum.ACCOUNT_RELATION_LIST_CACHE.getExpireTime());
    }

    private String getRelationListRedisKey(String sourceAccount, Integer relationType) {
        String prefix = "DATA";
        return this.getRelationListRedisKey(prefix, sourceAccount, relationType);
    }

    private String getRelationListRedisKey(String prefix, String sourceAccount, Integer relationType) {
        String typePrefix = "T";
        String accountPrefix = "AC";
        return RedisKeyEnum.ACCOUNT_RELATION_LIST_CACHE.getKey(
                prefix,
                typePrefix,
                String.valueOf(relationType),
                accountPrefix,
                sourceAccount);
    }


    /**
     * 根据关系ID获取实体信息
     *
     * @param relationId
     * @return
     */
    public AccountRelationEntity getRelationEntityById(Long relationId) {

        return accountRelationDAO.findById(relationId).orElse(null);
    }

    /**
     * 更新关系实体信息
     *
     * @param updateAccountRelationEntity
     * @return
     */
    public AccountRelationEntity updateAccountRelationEntity(AccountRelationEntity updateAccountRelationEntity) {

        Assert.notNull(updateAccountRelationEntity, "entity is null!");
        return accountRelationDAO.save(updateAccountRelationEntity);
    }

    /**
     * 查询数据库，获取账号的好友/订阅列表
     *
     * @param account
     * @return
     */
    public List<AccountRelationEntity> getAccountRelationList(String account, Integer type) {

        return accountRelationDAO.getAccountRelations(account, type, AccountRelationStatusEnum.CONFIRMED.getStatus());
    }

    /**
     * 执行订阅的持久化更新逻辑
     *
     * @param sourceAccount
     * @param targetAccount
     * @return
     */
    private Long doSubscribe(String sourceAccount, String targetAccount) {
        AccountRelationEntity entity = this.getAccountRelationEntity(sourceAccount, targetAccount);
        Assert.isTrue(entity == null || !AccountRelationStatusEnum.isConfirmed(entity.getStatus()), "重复订阅！");
        if (entity != null) {
            Date now = new Date();
            entity.setStatus(getSubscribeStatus().getStatus());
            entity.setUpdateDate(now);
            entity.setCreateDate(now);
        } else {
            entity = buildAccountRelationEntity(sourceAccount, targetAccount, this.getSubscribeStatus());
        }
        return this.saveAccountRelation2DB(entity);
    }

    /**
     * 执行取消订阅的持久化更新逻辑
     * @param sourceAccount
     * @param targetAccount
     * @return
     */
    private Long doUnSubscribe(String sourceAccount, String targetAccount) {
        AccountRelationEntity entity = this.getAccountRelationEntity(sourceAccount, targetAccount);
        Assert.isTrue(entity != null && AccountRelationStatusEnum.isConfirmed(entity.getStatus()), "未订阅，取消操作失败！");
        entity.setStatus(AccountRelationStatusEnum.DELETED.getStatus());
        entity.setUpdateDate(new Date());
        return this.saveAccountRelation2DB(entity);
    }

    private AccountRelationEntity getAccountRelationEntity(String sourceAccount, String targetAccount) {

        return accountRelationDAO.findAllBySourceAccountAndTargetAccount(sourceAccount, targetAccount);
    }

    private Long saveAccountRelation2DB(AccountRelationEntity entity) {

        return accountRelationDAO.save(entity).getId();
    }

    private AccountRelationEntity buildAccountRelationEntity(String sourceAccount, String targetAccount,
                                                             AccountRelationStatusEnum statusEnum) {
        AccountRelationEntity entity = new AccountRelationEntity();
        entity.setSourceAccount(sourceAccount);
        entity.setTargetAccount(targetAccount);
        entity.setStatus(statusEnum.getStatus());
        entity.setType(getRelationType().getType());
        entity.setCreateDate(new Date());
        entity.setUpdateDate(new Date());
        return entity;
    }

    /**
     * 一旦好友添加成功/公众号订阅成功等将 targetAccount添加到sourceAccount的最新对话列表缓存
     *
     * @param sourceAccount
     * @param targetAccount
     */
    protected void addTargetAccount2SourceLastChattingList(String sourceAccount, String targetAccount) {

        this.addTargetAccount2SourceLastChattingList(sourceAccount, targetAccount, System.currentTimeMillis());
    }

    protected void addTargetAccount2SourceLastChattingList(String sourceAccount, String targetAccount, Long lastTime) {
        /**
         * 好友关系订阅成功，走MQ通知UGC服务，需要刷新对话列表缓存
         */
        ChatMessageRequestVO message = new ChatMessageRequestVO();
        message.setSenderId(sourceAccount);
        message.setReceiverId(targetAccount);
        message.setTime(lastTime);
        messageQueueProducer.send(MessageQueueEnum.QUEUE_CHATTING_LIST_REFRESH, message);
    }
}
